﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Oops.Xna.Framework.Graphics
{
	// TODO : Not completely functioning yet. issues with backwards.
	/// <summary>
	/// Maintains the current animation state of a <see cref="SkinningData"/> instance by using
	/// the keyframe information.
	/// </summary>
	public class KeyframeAnimationPlayer : AnimationPlayer 
	{
		private int currentKeyframeIndex;

		private Matrix[] boneTransforms;
		private Matrix[] worldTransforms;
		private Matrix[] skinTransforms;

		/// <summary>
		/// Initializes a new instance of the <see cref="KeyframeAnimationPlayer"/> class.
		/// </summary>
		/// <param name="skinningData">The associated <see cref="SkinningData"/>.</param>
		public KeyframeAnimationPlayer(SkinningData skinningData) : base(skinningData)
		{
			// Create the transformation storage.
			this.boneTransforms = new Matrix[skinningData.BindPose.Count];
			this.worldTransforms = new Matrix[skinningData.BindPose.Count];
			this.skinTransforms = new Matrix[skinningData.BindPose.Count];
		}

		/// <summary>
		/// Gets the bone transformations of the <see cref="KeyframeAnimationPlayer"/>.
		/// </summary>
		public Matrix[] BoneTransforms
		{
			get { return this.boneTransforms; }
		}

		/// <summary>
		/// Gets the world transformations of the <see cref="KeyframeAnimationPlayer"/>.
		/// </summary>
		public Matrix[] WorldTransforms
		{
			get { return this.worldTransforms; }
		}

		/// <summary>
		/// Gets the skin transformations of the <see cref="KeyframeAnimationPlayer"/>.
		/// </summary>
		public Matrix[] SkinTransforms
		{
			get { return this.skinTransforms; }
		}

		#region AnimationPlayer Members

		public override void AdjustEffect(Effect effect)
		{
			// Assign to effect shader constants.
			effect.Parameters["SkinTransforms"].SetValue(this.SkinTransforms);

			// Call inherited method.
			base.AdjustEffect(effect);
		}

		protected override void OnCurrentClipChanged(EventArgs e)
		{
			// TODO : Not correct.
			this.currentKeyframeIndex = 0;

			// Initialize bone transforms to the bind pose.
			this.SkinningData.BindPose.CopyTo(this.boneTransforms, 0);

			// Call inherited method.
			base.OnCurrentClipChanged(e);
		}

		protected override void OnCurrentTimeChanged(EventArgs e)
		{
			// If the position moved backwards, reset the keyframe index.
			if (this.CurrentTime < this.PreviousTime)
			{
				this.currentKeyframeIndex = 0;
				this.SkinningData.BindPose.CopyTo(this.boneTransforms, 0);
			}

			// Update transforms.
			this.UpdateBoneTransforms();
			this.UpdateWorldTransforms(Matrix.Identity, this.boneTransforms);
			this.UpdateSkinTransforms();
			
			// Call inherited method.
			base.OnCurrentTimeChanged(e);
		}

		#endregion

		/// <summary>
		/// Updates the bone transforms for the current animation time.
		/// </summary>
		private void UpdateBoneTransforms()
		{
			while (this.currentKeyframeIndex < this.CurrentClip.Keyframes.Count)
			{
				Keyframe keyframe = this.CurrentClip.Keyframes[this.currentKeyframeIndex];
				if (keyframe.Time > this.CurrentTime)
				{
					break;
				}

				this.boneTransforms[keyframe.BoneIndex] = keyframe.Transform;
				this.currentKeyframeIndex++;
			}
		}

		/// <summary>
		/// Update the world transforms for the current animation time.
		/// </summary>
		/// <param name="rootTransform"></param>
		/// <param name="boneTransforms"></param>
		private void UpdateWorldTransforms(Matrix rootTransform, Matrix[] boneTransforms)
		{
			// Root bone.
			this.worldTransforms[0] = boneTransforms[0] * rootTransform;

			// Child bones.
			for (int i = 1; i < this.worldTransforms.Length; i++)
			{
				int parentBone = this.SkinningData.SkeletonHierarchy[i];

				this.worldTransforms[i] = 
					boneTransforms[i] * 
					this.worldTransforms[parentBone];
			}
		}

		/// <summary>
		/// Updates the skin transforms for the current animation time.
		/// </summary>
		private void UpdateSkinTransforms()
		{
			for (int i = 0; i < this.skinTransforms.Length; i++)
			{
				this.skinTransforms[i] = 
					this.SkinningData.InverseBindPose[i] * 
					this.worldTransforms[i];
			}
		}
	}
}
